
import re
from configobj import ConfigObj
from datetime import *
import _pg

class ProjectError(Exception):
    def __init__(self, msg):
        self.parameter = msg
    def __str__(self):
        print self.parameter

class StateFailure(ProjectError):
    def __init__(self, state, msg):
        self.parameter = "State could not be reached: " % state + msg

class ProjectParameterError(ProjectError):
    def __init__(self, msg):
        self.parameter = "Parameter: " + msg + " not defined in project file!"

class Project(ConfigObj):
    def __init__(self, configfile, projectDir):
        """ Load data from configobj generated config file """
        ConfigObj.__init__(self, projectDir+configfile)
        self.projectDir = projectDir
        #self.filename = configfile
        self.loadDeps()
    
    def save(self):
        """ Saves project state to config file """
        self.write()

    def loadDeps(self):
        """ Loads deps from another config file """
        if not self.has_key('depsFile'):
            raise ProjectParameterError("depsFile")
        if not self.has_key('states'):
            self['states'] = {}
        #load dependency configobj file
        self.depsDict = ConfigObj(self.projectDir + self['depsFile'])
        #a comma is a "or" and a + is a "and" with and being reduced first. 
        for state, deps in self.depsDict.items():   
            if deps == '':
                #no deps to get into this state
                pass
            elif isinstance(deps, list):
                result = []
                #multiple possible dep sets, store double list
                #where [[1,2],[3,4]] reads as: (1 and 2) or (3 and 4)
                for depset in deps:
                    depset = re.sub('\s', '', depset) #kill whitespace
                    depset = depset.split('+')
                    result.append(depset)
                self.depsDict[state] = result
            else: 
                #deps is a single set of states and'ed together
                deps = re.sub('\s', '', deps)
                deps = deps.split('+')
                self.depsDict[state] = [deps] #store as [[state, state, ...]]

        
    def getPathToState(self, state, firstRun=True):
        """ Returns ordered list of states which must be calculated to reach 'state', in order. Prompts user on OR choicepoints."""

        stack = [state]
        #why is state a list here? 
        if self.depsDict[state] == '':
            pass
        elif len(self.depsDict[state]) == 1: 
            #one and set, recurse into it's states
            for dep in self.depsDict[state][0]:
                stack.extend(self.getPathToState(dep, firstRun=False))
        else:
            #choice point: which OR path do we go down?
            index = 0
            print self.depsDict[state]
            for andset in self.depsDict[state]:
                print str(index) + ": " + str(andset)
                index += 1

            print "Select the number of the dependancy set you want to use:"
            selection = int(raw_input(">"))
            while not ( selection >= 0 and selection < len(self.depsDict[state])):
                print "Invalid selection, try again:"
                selection = (raw_input(">"))
                
            for dep in self.depsDict[state][int(selection)]:
                stack.extend(self.getPathToState(dep, firstRun=False))
        
        if firstRun == True:
            #post processing of data, remove duplicate elements
            processedStates = []
            #this loop just removes all but the last instance of any state from the stack (right now, end of list will be processed first)
            for e in stack:
                if stack.count(e) > 1:
                    for ii in range(stack.count(e) - 1):
                        stack.remove(e)
            stack.reverse()
                    
        return stack

    def log(self, logstring):
        """ logs logstring into Project """
        #TODO: maximum size limit
        logstring = re.sub('\n', ' ', logstring)
        if self.has_key('log'):
            self['log'] += '\n' + str(datetime.now()) + ": " + logstring
        else:
            self['log'] = str(datetime.now()) + ": " + logstring

    def getDepsDict(self):
        return self.depsDict
    
    def isInState(self, state):
        return ( self.has_key("states") and self["states"].has_key(state) )

    def printStates(self):
        """ Prints the state information in a human readable format."""
        for state in self.depsDict.keys(): #go through all states possible
            if self.isInState(state):
                print "*", state, "[started:",self["states"][state]["starttime"]+"]"
            else:
                print " ", state

    def canEnterState(self, state):
        """ Returns true if project has deps satisfied to move to state specificied"""
        
        #check if this state even exists in our ontology
        if not self.depsDict.has_key(state):
            raise ProjectError("State %s does not exist in dependancy file" % state)
        
        deplist = self.depsDict[state]

        for andset in deplist: #andset is a list of dependancies "and'ed" together
            setSatisfied = True
            #make sure every dep in the 'and' set checks out, otherwise whole set fails
            for dep in andset:
                if not self['states'].has_key(dep):
                    setSatisfied = False 
            if setSatisfied == True: #if a whole 'and' set survived, then "state" checks out
                return True
        return False
        
    def enterState(self, statename, starttimestamp, endtimestamp, notes):
        """ Moves the project to given statename """
        if not self.validState(statename):
            raise ProjectError("Project does not know about state: " + statename)
        if not self.has_key("states"):
            self["states"] = {}
        self["states"][statename] = {}
        self["states"][statename]["starttime"] = starttimestamp.isoformat()
        self["states"][statename]["endtime"] = endtimestamp.isoformat()
        self["states"][statename]["notes"] = notes

    def exitState(self, statename):
        """ Tells project to leave given state """
        if not self.validState(statename):
            raise ProjectError("Project does not know about state: " + statename)
        if self["states"].has_key(statename):
            del self["states"][statename]
        
    def validState(self, state):
        return self.depsDict.has_key(state)

    def addDeps(self, stack, state):
        #delete this state from stack if already there
        try:
            i = stack.index(state)
            del stack[i]
        except ValueError:
            pass #state wasn't in stack

        stack.append(state)
        for d in deps[state]:
            if d != '':
                addDeps(stack, d, deps)

    def getProcessPlan(self, destState):
        """ Takes in a destinatino state, returns list of states that must be reached in order."""
        procPlan = self.getPathToState(destState)
        plan = []
        recalc = None
        for state in procPlan:
            if not self['states'].has_key(state):
                plan.append(state)
            else:
                if recalc == None:
                    answer = raw_input("Do you want to recalculate any states? (y/N) >")
                    answer = answer + " "
                    if(answer[0] == "Y" or answer[0] == "y"):
                        recalc = True
                    else:
                        recalc = False
                if recalc == True:
                    answer = raw_input("Do you want to recalculate state %s? (y/N) >" %state)
                    answer = answer + " "
                    if(answer[0] == "Y" or answer[0] == "y"):
                        plan.append(state)
        if destState not in plan:
            plan.append(destState)
        return plan


